﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Cysharp.Threading.Tasks;
using LZX.Main.HotUpdate.ResourceData;
using LZX.MScriptableObject;
using UnityEditor;
using UnityEngine;
#if UNITY_EDITOR
using LZX.MEditor.Enum;
using LZX.MEditor.LZXStatic;
#endif

namespace LZX.HotUpdate
{
    public class LZXResources
    {
        private static LZXResources _instance;
        public static LZXResources Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new LZXResources();
                }
                return _instance;
            }
        }
        public void Init()
        {
            //TODO:初始化
        }
        public VersionObject Version;
        public static void ParseVersionObject(VersionObject version)
        {
            Instance.Parse(version);
        }
        /// <summary>
        /// 快速索引资产位置的字典
        /// </summary>
        internal readonly Dictionary<string,AssetData> m_Assets = new Dictionary<string, AssetData>();
        /// <summary>
        /// 快速索引Bundle的字典
        /// </summary>
        internal readonly Dictionary<string, Bundle> m_Dlls = new Dictionary<string, Bundle>();
        /// <summary>
        /// 已经加载的Bundle
        /// </summary>
        internal readonly Dictionary<string, BundleData> LoadedBundles = new Dictionary<string, BundleData>();
        private void Parse(VersionObject version)
        {
            Version = version;
            foreach (var bundle in version.Bundles)
            {
                foreach (var asset in bundle.Assets)
                {
                    AssetData data = new AssetData()
                    {
                        LoadPath = asset.LoadPath,
                        BundleName = bundle.Name,
                        Dependencies = (string[])asset.Dependencies.Clone()
                    };
                    m_Assets.Add(Path.GetFileNameWithoutExtension(data.LoadPath), data);
                }
                if(bundle.Name.EndsWith(".dll.bytes"))
                    m_Dlls.Add(bundle.Name.Replace(".dll.bytes", ""), bundle);
            }
#if LZXDebug
            using (MemoryStream stream = new MemoryStream())
            {
                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                formatter.Serialize(stream,m_Assets);
                Debug.Log($"Size-m_Assets:{stream.Length}/bytes");
            }
            using (MemoryStream stream = new MemoryStream())
            {
                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                formatter.Serialize(stream,m_Dlls);
                Debug.Log($"Size-m_Assets:{stream.Length}/bytes");
            }
#endif
        }
        private async UniTask<T> LoadAsset<T>(string assetName) where T : UnityEngine.Object
        {
            if (Version == null)
                throw new Exception("Please call LZXResources.Init first");
            if (!m_Assets.TryGetValue(assetName, out var data))
                throw new Exception($"Asset:{assetName} not found");
            if (data.Dependencies != null && data.Dependencies.Length > 0)
            {
                foreach (var dependency in data.Dependencies)
                {
                    await LoadAsset<UnityEngine.Object>(Path.GetFileNameWithoutExtension(dependency));
                }
            }
            if (!LoadedBundles.TryGetValue(data.BundleName, out var bundleData))
            {
                string path = Path.Combine(Application.persistentDataPath,
                    Version.version,
                    data.BundleName + Version.BundleEx);
                var req = AssetBundle.LoadFromFileAsync(path);
                await req;
                bundleData = new BundleData(req.assetBundle);
                LoadedBundles.Add(data.BundleName, bundleData);
            }
            return await bundleData.LoadAssetAsync<T>(data.LoadPath);
        }
        public static void UnLoadAsset(UnityEngine.Object obj)
        {
            if (Instance.Version == null)
                throw new Exception("Please call LZXResources.Init first");
            if(Instance.LoadedBundles.TryGetValue(obj.name,out var bundleData))
                bundleData.UnLoadAsset(obj);
        }
        public static async UniTask<T> LoadAssetAsync<T>(string assetName) where T : UnityEngine.Object
        {
            T obj = null;
#if UNITY_EDITOR
            switch (LoadStatuHelper.Statu)
            {
                case LoadStatu.DEFAULT:
                    obj = Instance.EditorLoadAsset<T>(assetName);
                    break;
                case LoadStatu.SIMULATION:
                    obj = await Instance.LoadAsset<T>(assetName);
                    break;
            }
#else
            obj = await Instance.LoadAsset<T>(assetName);
#endif
            return obj;
        }
        public static async UniTask<Assembly> LoadDllAsync(string dllName)
        {
            if (Instance.Version == null)
                throw new Exception("Please call LZXResources.ParseVersionObject first");
            if (Instance.m_Dlls.TryGetValue(dllName, out var bundle))
            {
                string path = Path.Combine(Application.persistentDataPath,
                    Instance.Version.version, bundle.Name);
                if (!File.Exists(path))
                    throw new Exception($"Dll:{path} not found");
#if !UNITY_EDITOR
                //非编辑器下加载程序集
                var bytes = await File.ReadAllBytesAsync(path);
                Assembly assembly = Assembly.Load(bytes);
#else
                Assembly assembly = System.AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == dllName);
#endif
                return assembly;
            }
            Debug.LogError($"Dll:{dllName} not found");
            return null;
        }
        public static void UnLoadBundle(string bundleName,bool UnloadUnloadedAssets)
        {
            if (Instance.LoadedBundles.TryGetValue(bundleName, out var bundleData))
            {
                bundleData.Unload(UnloadUnloadedAssets);
                Instance.LoadedBundles.Remove(bundleName);
            }
        }
#if UNITY_EDITOR
        private T EditorLoadAsset<T>(string assetName) where T : UnityEngine.Object
        {
            AssetData data;
            if (Instance.Version == null)
            {
                var versionController = LZXEditorResources.GetVersionController();
                if (versionController == null)
                    throw new Exception("Please create version controller first");
                foreach (var kv in versionController._bundles)
                {
                    foreach (var asset in kv.Value.AssetGUIDs)
                    {
                        string loadPath = AssetDatabase.GUIDToAssetPath(asset);
                        data = new AssetData()
                        {
                            LoadPath = loadPath,
                            BundleName = kv.Key,
                            Dependencies = LZXEditorResources.GetDependencies(loadPath).ToArray()
                        };
                        m_Assets.Add(Path.GetFileNameWithoutExtension(data.LoadPath), data);
                    }
                }
                Instance.Version = ScriptableObject.CreateInstance<VersionObject>();
            }
            if(!Instance.m_Assets.TryGetValue(assetName,out data))
                throw new Exception($"Asset:{assetName} not found");
            return AssetDatabase.LoadAssetAtPath<T>(data.LoadPath);
        }
#endif
        
        //加载AssetBundle
        public static async UniTask<AssetBundle> LoadAssetBundleAsync(string bundleName)
        {
            if (!Instance.LoadedBundles.TryGetValue(bundleName, out var bundleData))
            {
                string path = Path.Combine(Application.persistentDataPath,
                    Instance.Version.version,
                    bundleName + Instance.Version.BundleEx);
                var req = AssetBundle.LoadFromFileAsync(path);
                await req;
                bundleData = new BundleData(req.assetBundle);
                Instance.LoadedBundles.Add(bundleName, bundleData);
            }
            return bundleData.Bundle;
        }
    }
}